package com.tcm.service.impl;

import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tcm.common.enums.CachePrefix;
import com.tcm.config.AppProperties;
import com.tcm.entity.SocialClazz;
import com.tcm.entity.SocialProject;
import com.tcm.entity.SocialStudent;
import com.tcm.entity.User;
import com.tcm.entity.bo.SocialStudentExcel;
import com.tcm.entity.dto.SocialStudentUpdateDto;
import com.tcm.entity.dto.param.SocialStudentPageParam;
import com.tcm.entity.dto.param.SocialStudentUpdateParam;
import com.tcm.entity.vo.BatchImportResult;
import com.tcm.entity.resp.PageR;
import com.tcm.entity.vo.SocialStudentListResult;
import com.tcm.exception.DatabaseOperationException;
import com.tcm.exception.EntityAlreadyExistException;
import com.tcm.exception.EntityInvalidException;
import com.tcm.mapper.SocialStudentMapper;
import com.tcm.service.SocialClazzService;
import com.tcm.service.SocialProjectService;
import com.tcm.service.SocialStudentService;
import com.tcm.service.UserService;
import com.tcm.utils.ExcelExportUtil;
import com.tcm.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 社会化学员业务实现类
 * @author Guqier
 * @version 1.0
 * @date 2023/7/23 19:45
 */
@Slf4j
@Service
public class SocialStudentServiceImpl extends ServiceImpl<SocialStudentMapper, SocialStudent> implements SocialStudentService {

    @Autowired
    private SocialClazzService socialClazzService;

    @Autowired
    private UserService userService;

    @Autowired
    private AppProperties appProperties;

    @Autowired
    private SocialProjectService socialProjectService;

    public static final Integer SOCIAL_STUDENT_USER_TYPE = 3;// 社会化学员用户类型

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 添加社会化学员
     * @param dto 社会化学员数据更新封装类
     * @param currUserId 当前登录用户ID
     * @return 处理结果，true为成功
     */
    @Override
    @Transactional
    public boolean addSocialStudent(SocialStudentUpdateDto dto, Long currUserId){
        // 校验账号是否存在
        User user1 = userService.getUserByAccount(dto.getAccount());
        if (user1 != null) {
            throw new EntityAlreadyExistException("账号已存在");
        }
        // 校验班级是否存在
        SocialClazz socialClazz = socialClazzService.getById(dto.getClazzId());
        if (socialClazz == null) {
            return false;
        }
        // 保存社会化学员实体类
        SocialStudent socialStudent = new SocialStudent();
        BeanUtils.copyProperties(dto, socialStudent);
        socialStudent.setCreateUser(currUserId);
        socialStudent.setUpdateUser(currUserId);
        boolean bs = this.save(socialStudent);
        // 保存用户信息
        User user = new User();
        user.setAccount(dto.getAccount());
        user.setName(dto.getName());
        user.setNickname(dto.getName());
        user.setUserType(SOCIAL_STUDENT_USER_TYPE);
        user.setGender(dto.getGender());
        user.setEmail(dto.getEmail());
        user.setPhone(dto.getPhone());
        String salt = UUID.randomUUID().toString();
        String pwd = DigestUtils.md5DigestAsHex((salt + appProperties.getDefaultPasswordStudent()).getBytes());
        user.setSalt(salt);
        user.setPassword(pwd);
        user.setDisableTime(socialClazz.getInvalidTime());
        boolean bu = userService.save(user);
        if (!bs || !bu){
            throw new DatabaseOperationException("数据库操作失败");
        }
        return true;
    }

    /**
     * 修改社会化学员
     * @param param 尝试封装
     * @param currUserId 当前登录用户ID
     * @return 处理结果，true为成功
     */
    @Override
    @Transactional
    public boolean modifySocialStudent(SocialStudentUpdateParam param, Long currUserId){
        // 校验学员是否存在
        SocialStudent socialStudent = this.getById(param.getId());
        if (socialStudent == null) {
            return false;
        }
        // 校验班级是否存在
        if (!socialStudent.getClazzId().equals(param.getClazzId())) {
            throw new EntityInvalidException("班级不存在");
        }
        // 修改社会化学员信息实体
        BeanUtils.copyProperties(param, socialStudent, "id", "account");
        socialStudent.setUpdateUser(currUserId);
        boolean bs = this.updateById(socialStudent);
        // 修改用户实体
        User user = userService.getUserByAccount(socialStudent.getAccount());
        BeanUtils.copyProperties(param, user, "id", "account");
        user.setUpdateUser(currUserId);
        boolean bu = userService.updateById(user);

        if (!bs || !bu){
            throw new DatabaseOperationException("数据库操作失败");
        }
        redisUtil.deleteObject(CachePrefix.PC_USER_INFO.getCacheKey(user.getAccount()));
        return true;
    }

    /**
     * 删除社会化学员
     * @param ids 社会化学员ID列表
     * @param currUserId 当前登录用户ID
     * @return 处理结果，true为成功
     */
    @Override
    @Transactional
    public boolean removeSocialStudent(List<Long> ids, Long currUserId){
        int cnt = 0;// 用于计数
        for (Long id : ids) {
            // 删除学员信息
            SocialStudent socialStudent = this.getById(id);
            socialStudent.setUpdateUser(currUserId);
            boolean bs = this.updateById(socialStudent);
            boolean bsd = this.removeById(socialStudent.getId());
            // 删除用户信息
            User user = userService.getUserByAccount(socialStudent.getAccount());
            user.setUpdateUser(currUserId);
            boolean bu = userService.updateById(user);
            boolean bud = userService.removeById(user.getId());

            if (bs && bsd && bu && bud){
                cnt++;
            }else {
                break;
            }
            redisUtil.deleteObject(CachePrefix.PC_USER_INFO.getCacheKey(user.getAccount()));
        }
        if (cnt != ids.size()){
            throw new DatabaseOperationException("数据库操作失败");
        }
        return true;
    }

    /**
     * 分页获取社会化学员列表
     * @param param 分页参数
     * @return 社会化学员列表
     */
    @Override
    public PageR<SocialStudentListResult> getSocialStudentListByPage(SocialStudentPageParam param){
        // 构造查询条件
        Page<SocialStudent> page = new Page<>(param.getPage(), param.getPageSize());
        page.addOrder(OrderItem.desc("update_time"));
        LambdaQueryWrapper<SocialStudent> queryWrapper = buildSocialStudentQueryWrapper(param);
        // 查询符合条件的社会化学员
        this.page(page, queryWrapper);
        // 准备数据
        List<User> users = userService.list();
        List<SocialProject> socialProjectList = socialProjectService.list();
        List<SocialClazz> clazzList = socialClazzService.list();
        // 构造结果集
        PageR<SocialStudentListResult> pageR = new PageR<>();
        BeanUtils.copyProperties(page, pageR, "records");
        pageR.setRecords(transferSocialStudentToListResult(page.getRecords()));
        return pageR;
    }

    /**
     * 将社会化学员对象转换为列表数据展示类
     * @param socialStudents 社会化学员对象
     * @return 列表数据展示类列表数据展示类
     */
    @Override
    public List<SocialStudentListResult> transferSocialStudentToListResult(List<SocialStudent> socialStudents){
        // 准备数据
        List<User> users = userService.list();
        List<SocialProject> socialProjectList = socialProjectService.list();
        List<SocialClazz> clazzList = socialClazzService.list();
        // 构造结果集
        return socialStudents.stream().map(socialStudent -> {
            SocialStudentListResult result = new SocialStudentListResult();
            BeanUtils.copyProperties(socialStudent, result);
            users.stream().filter(user -> user.getAccount().equals(socialStudent.getAccount())).findFirst().ifPresent(user -> BeanUtils.copyProperties(user, result, "id"));
            clazzList.stream().filter(socialClazz -> socialClazz.getId().equals(socialStudent.getClazzId())).findFirst().ifPresent(socialClazz -> {
                result.setProjectId(socialClazz.getProjectId());
                result.setClazzName(socialClazz.getName());
                socialProjectList.stream().filter(socialProject -> socialProject.getId().equals(socialClazz.getProjectId())).findFirst().ifPresent(socialProject -> result.setProjectName(socialProject.getName()));
            });
            return result;
        }).collect(Collectors.toList());
    }

    /**
     * 批量导入社会化信息信息校验
     * @param file excel文件
     * @return 上传结果
     * @throws IOException 异常
     */
    @Override
    public BatchImportResult<SocialStudentExcel> importSocialStudentInfo(MultipartFile file) throws IOException {
        // 解析Excel文件
        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
        reader.addHeaderAlias("项目", "projectName");
        reader.addHeaderAlias("班级", "clazzName");
        reader.addHeaderAlias("账号", "account");
        reader.addHeaderAlias("姓名", "name");
        reader.addHeaderAlias("性别", "gender");
        reader.addHeaderAlias("证件号", "cardId");
        reader.addHeaderAlias("邮箱", "email");
        reader.addHeaderAlias("电话", "phone");
        List<SocialStudentExcel> socialStudentExcels = reader.readAll(SocialStudentExcel.class);

        // 准备数据
        List<SocialProject> projectList = socialProjectService.list();
        List<SocialClazz> clazzList = socialClazzService.list();
        List<SocialStudent> studentList = this.list();
        // 构造结果集
        BatchImportResult<SocialStudentExcel> results = new BatchImportResult<>();
        List<SocialStudentExcel> successList = new ArrayList<>();
        List<SocialStudentExcel> failList = new ArrayList<>();
        List<SocialStudentUpdateDto> dtoList = new ArrayList<>();
        // 项目已有学生数统计
        Map<Long, Integer> stuNum = new HashMap<>();// <项目ID, 学生数>
        // 检验并储存信息
        for (SocialStudentExcel excel : socialStudentExcels) {
            // 检验账号是否已存在
            LambdaQueryWrapper<SocialStudent> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SocialStudent::getAccount, excel.getAccount());
            SocialStudent one = this.getOne(queryWrapper);
            if (one != null){
                excel.setMsg("账号已存在");
                failList.add(excel);
                continue;
            }
            // 检验社会化项目是否存在
            Optional<SocialProject> projectOptional = projectList.stream().filter(socialProject -> socialProject.getName().equals(excel.getProjectName())).findFirst();
            if (!projectOptional.isPresent()){
                excel.setMsg("项目不存在");
                failList.add(excel);
                continue;
            }
            // 校验社会化班级是否存在
            Optional<SocialClazz> clazzOptional = clazzList.stream()
                    .filter(socialClazz -> socialClazz.getProjectId().equals(projectOptional.get().getId()))// 查找指定项目的班级
                    .filter(socialClazz -> socialClazz.getName().equals(excel.getClazzName())).findFirst();// 查找指定班级
            if (!clazzOptional.isPresent()) {
                excel.setMsg("班级不存在");
                failList.add(excel);
                continue;
            }
            // 统计当前项目已有学生数
            SocialProject project = projectOptional.get();
            Integer cnt = stuNum.get(project.getId());
            if (cnt == null) {
                // 如果没有统计过，则统计一次
                cnt = new Long(studentList
                        .stream()
                        .filter(socialStudent -> clazzList
                                .stream()
                                .filter(socialClazz -> socialClazz.getProjectId().equals(project.getId()))
                                .map(SocialClazz::getId)
                                .collect(Collectors.toList())
                                .contains(socialStudent.getClazzId()))
                        .count()).intValue();
                stuNum.put(project.getId(), cnt);
            }
            // 构造Dto
            SocialStudentUpdateDto dto = new SocialStudentUpdateDto();
            BeanUtils.copyProperties(excel, dto, "gender", "num");
            dto.setGender(excel.getGender().equals("男") ? 1 : 0);
            dto.setClazzId(clazzOptional.get().getId());
            dto.setNum(project.getNum() + String.format("%03d", cnt + 1));
            stuNum.put(project.getId(), cnt + 1);
            // 保存到结果集
            dtoList.add(dto);
            excel.setNum(dto.getNum());
            excel.setMsg("校验通过");
            successList.add(excel);
        }
        results.setUuid(UUID.randomUUID().toString());
        results.setTotal(socialStudentExcels.size());
        results.setSuccessCount(successList.size());
        results.setSuccessList(successList);
        results.setFailCount(failList.size());
        results.setFailList(failList);
        // 存入缓存，等待前端确认，超过5分钟自动清除
        redisUtil.setCacheObject(CachePrefix.PC_IMPORT_SOCIAL_STUDENT_RES.getCacheKey(results.getUuid()), results, 5, TimeUnit.MINUTES);
        redisUtil.setCacheObject(CachePrefix.PC_IMPORT_SOCIAL_STUDENT_DTO.getCacheKey(results.getUuid()), dtoList, 5, TimeUnit.MINUTES);
        log.info("社会化学员信息上传ID：{}", results.getUuid());
        return results;
    }

    /**
     * 确认导入社会化学员信息
     * @param uuid 导入uuid
     * @return 导入结果，true为成功
     */
    @Override
    @Transactional
    public boolean confirmBatchSocialStudentInfo(String uuid, Long currUserId){
        // 从缓存中取出数据
        List<SocialStudentUpdateDto> dtoList = redisUtil.getCacheObject(CachePrefix.PC_IMPORT_SOCIAL_STUDENT_DTO.getCacheKey(uuid));
        if (dtoList == null) {
            return false;
        }
        // 导入数据
        int cnt = 0;
        for (SocialStudentUpdateDto dto : dtoList) {
            if (addSocialStudent(dto, currUserId)) {
                cnt ++;
            }
        }
        // 校验结果
        if (cnt != dtoList.size()){
            throw new DatabaseOperationException("数据库保存失败");
        }
        // 清除缓存
        redisUtil.deleteObject(CachePrefix.PC_IMPORT_SOCIAL_STUDENT_DTO.getCacheKey(uuid));
        return true;
    }

    /**
     * 导出导入失败名单
     * @param uuid 导入uuid
     * @param response http响应对象
     */
    @Override
    public void exportImportFailedSocialStudentInfo(String uuid, HttpServletResponse response) throws Exception {
        // 从缓存中获取数据
        BatchImportResult<SocialStudentExcel> result = redisUtil.getCacheObject(CachePrefix.PC_IMPORT_SOCIAL_STUDENT_RES.getCacheKey(uuid));
        if (result == null) {
            throw new Exception("缓存不存在或已过期");
        }
        exportSocialStudentCommon(response, result.getFailList());
    }

    /**
     * 根据检索信息导出社会化学员列表
     * @param param 检索信息
     * @param response http 响应对象
     * @return 导出结果，true为成功
     */
    @Override
    public boolean exportSocialStudentInfo(SocialStudentPageParam param, HttpServletResponse response){
        // 构造查询条件
        LambdaQueryWrapper<SocialStudent> queryWrapper = buildSocialStudentQueryWrapper(param);
        List<SocialStudent> socialStudents = this.list(queryWrapper);
        // 准备数据
        List<SocialProject> projectList = socialProjectService.list();
        List<SocialClazz> clazzList = socialClazzService.list();
        List<SocialStudentExcel> excels = socialStudents.stream().map(socialStudent -> {
            // 获取用户信息
            User user = userService.getUserByAccount(socialStudent.getAccount());
            // 构造Excel对象
            SocialStudentExcel excel = new SocialStudentExcel();
            BeanUtils.copyProperties(socialStudent, excel, "gender");
            excel.setGender(user.getGender() == 1 ? "男" : "女");
            excel.setEmail(user.getEmail());
            excel.setPhone(user.getPhone());
            clazzList.stream().filter(socialClazz -> socialClazz.getId().equals(socialStudent.getClazzId())).findFirst().ifPresent(socialClazz -> {
                // 查找社会化班级
                excel.setClazzName(socialClazz.getName());
                // 查找社会化项目
                projectList.stream().filter(socialProject -> socialProject.getId().equals(socialClazz.getProjectId())).findFirst().ifPresent(socialProject -> excel.setProjectName(socialProject.getName()));
            });
            excel.setMsg("正常导出");
            return excel;
        }).collect(Collectors.toList());
        // 响应导出
        try {
            exportSocialStudentCommon(response, excels);
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    /**
     * 社会化学员通用导出
     * @param response http响应对象
     * @param excels 导出数据
     * @throws IOException 异常
     */
    public void exportSocialStudentCommon(HttpServletResponse response, List<SocialStudentExcel> excels) throws IOException {
        // 设置导出列名
        ExcelWriter writer = ExcelUtil.getWriter();
        writer.addHeaderAlias("projectName", "项目");
        writer.addHeaderAlias("clazzName", "班级");
        writer.addHeaderAlias("num", "编号");
        writer.addHeaderAlias("account", "账号");
        writer.addHeaderAlias("name", "姓名");
        writer.addHeaderAlias("gender", "性别");
        writer.addHeaderAlias("cardId", "证件号");
        writer.addHeaderAlias("email", "邮箱");
        writer.addHeaderAlias("phone", "电话");
        writer.addHeaderAlias("msg", "导入/导出状态");
        // 响应数据
        writer.write(excels, true);

        ExcelExportUtil.commonExport(writer, response);
    }

    /**
     * 构造社会化学员查询条件
     * @param param 查询参数
     * @return LambdaQueryWrapper
     */
    public LambdaQueryWrapper<SocialStudent> buildSocialStudentQueryWrapper(SocialStudentPageParam param){
        LambdaQueryWrapper<SocialStudent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StringUtils.hasText(param.getAccount()), SocialStudent::getAccount, param.getAccount());
        queryWrapper.eq(StringUtils.hasText(param.getName()), SocialStudent::getName, param.getName());
        queryWrapper.eq(StringUtils.hasText(param.getCardId()), SocialStudent::getCardId, param.getCardId());
        queryWrapper.eq(param.getClazzId() != null, SocialStudent::getClazzId, param.getClazzId());
        return queryWrapper;
    }

    /**
     * 通过账号获取社会化学员信息
     * @param account 账号
     * @return 社会化学员信息
     */
    @Override
    public SocialStudent getSocialStudentByAccount(String account){
        LambdaQueryWrapper<SocialStudent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SocialStudent::getAccount, account);
        return this.getOne(queryWrapper);
    }
}
